/*
 * Copyright (c) 2019-2029, <a href="https://wwww.intellijboy.cn">https://wwww.intellijboy.cn</a> (liuburu@qq.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * <a href="https://www.apache.org/licenses/LICENSE-2.0">https://www.apache.org/licenses/LICENSE-2.0</a>
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package open.intellijboy.core.configuration.swagger;

import io.swagger.v3.oas.annotations.tags.Tag;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

/**
 * swagger group 加载器
 *
 * @author intellijboy
 * @date 2023/10/3
 * @since 1.0
 */
public class SwaggerGroupLoader implements BeanDefinitionRegistryPostProcessor {
    private ConfigurableListableBeanFactory beanFactory;

    private Class<?> mainApplicationClass;


    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // do nothing
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        this.mainApplicationClass = deduceMainApplicationClass();
        this.beanFactory = beanFactory;
        // 自动注册swagger分组
        // 注入构建者
        Optional.of(getAnnotatedClasses(this.mainApplicationClass.getPackageName(), Controller.class))
                .ifPresent(this::registerGroupedOpenApi);
    }

    private void registerGroupedOpenApi(Set<Class> ctlClasses) {
        // 注入构建者
        ctlClasses.forEach(ctrl -> {
            Tag tagAnnotation = AnnotationUtils.findAnnotation(ctrl, Tag.class);
            if (tagAnnotation != null) {
                String beanName = String.format("%s.%s", ctrl.getSimpleName(), tagAnnotation.annotationType().getSimpleName());
                RequestMapping requestMapping = AnnotationUtils.findAnnotation(ctrl, RequestMapping.class);
                String pathsToMatch = null;
                if (requestMapping != null) {
                    String[] value = requestMapping.value();
                    if (value != null && value.length > 0) {
                        pathsToMatch = String.format("/%s/**", value[0]);
                    }
                }
                if (pathsToMatch.isEmpty()) {
                    pathsToMatch = "/**";
                }
                GroupedOpenApi item = GroupedOpenApi.builder().group(tagAnnotation.name()).pathsToMatch(pathsToMatch).build();
                this.beanFactory.registerSingleton(beanName, item);
            }
        });
    }

    private Set<Class> getAnnotatedClasses(String basePackage, Class annotationClass) {
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter(new AnnotationTypeFilter(annotationClass));
        Set<Class> annotatedClasses = new HashSet<>();
        for (BeanDefinition beanDefinition : scanner.findCandidateComponents(basePackage)) {
            try {
                Class<?> targetClass = Class.forName(beanDefinition.getBeanClassName());
                annotatedClasses.add(targetClass);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return annotatedClasses;
    }

    private Class<?> deduceMainApplicationClass() {
        return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
                .walk(this::findMainClass)
                .orElse(null);
    }

    private Optional<Class<?>> findMainClass(Stream<StackWalker.StackFrame> stack) {
        return stack.filter((frame) -> Objects.equals(frame.getMethodName(), "main"))
                .findFirst()
                .map(StackWalker.StackFrame::getDeclaringClass);
    }


}
